/* Software Name : AsmDex
 * Version : 1.0
 *
 * Copyright © 2012 France Télécom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ow2.asmdex;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.ow2.asmdex.instruction.IOffsetInstruction;
import org.ow2.asmdex.instruction.IPseudoInstruction;
import org.ow2.asmdex.instruction.Instruction;
import org.ow2.asmdex.instruction.InstructionFormat10X;
import org.ow2.asmdex.instruction.InstructionFormat20T;
import org.ow2.asmdex.instruction.InstructionFormat30T;
import org.ow2.asmdex.instruction.PseudoInstructionFillArrayData;
import org.ow2.asmdex.instruction.PseudoInstructionPackedSwitch;
import org.ow2.asmdex.instruction.PseudoInstructionSparseSwitch;
import org.ow2.asmdex.lowLevelUtils.InstructionEncoder;
import org.ow2.asmdex.structureCommon.Label;
import org.ow2.asmdex.structureCommon.LocalVariable;
import org.ow2.asmdex.structureWriter.AnnotationItem;
import org.ow2.asmdex.structureWriter.CodeItem;
import org.ow2.asmdex.structureWriter.ConstantPool;
import org.ow2.asmdex.structureWriter.ExceptionHandler;
import org.ow2.asmdex.structureWriter.Field;
import org.ow2.asmdex.structureWriter.Method;
import org.ow2.asmdex.structureWriter.Prototype;
import org.ow2.asmdex.structureWriter.TryCatch;

/**
 * Class that takes care of visiting all the elements of a method.
 * <br /><br />
 * RegisterSize, Incoming Arguments Size, Outgoing Arguments Size<br />
 * --------------------------------------------------------------<br />
 * The RegisterSize should be calculated like that :
 * <ul>
 * <li> First, the local registers (watching the pair registers according to the opcodes !).</li>
 * <li> Then, "this" if the method is non-static AND not a constructor.</li>
 * <li> Then, the parameters (1 (word) for int... 2 for double, long, references).</li>
 * </ul>
 * But we can't calculate it because we don't know if "this" or the parameters are used.
 * So we have to rely on visitMaxs() to give us the RegisterSize.
 * <br /><br />
 * The Incoming Argument Size : calculating it consists in parsing the descriptor, minus the
 * return type, and counting how many bytes are allocated to each type (I = 2 (bytes), D = 4...).
 * Add 2 for "this", unless it's a Static method.
 * <br /><br />
 * The Outgoing Arguments Size : a bit the same. Each time a Method is called inside the current
 * Method, calculates how many bytes are allocated to each type (I = 2 bytes, D = 4, L = 4...) in the
 * descriptor, minus the return type. Add 2 for "this", unless the called method is Static.
 * <br /><br />
 * Labels Management<br />
 * -----------------<br />
 * <ul>
 * <li>After the first pass, at the end of the visit of the Method (visitEnd), we know where all the labels
 *   are, IN THE ORIGINAL CODE.</li>
 * <li> The unconditional Jump instructions are encoded as they are given. They will of course be
 *   extended if needed.</li>
 * <li> We must know what are the instructions that refer to the Labels, in order to check if their
 *   range is enough. So each Label has a list of the instructions making reference to it and that are
 *   subject to be too far (Unconditional Jumps only. Conditional jumps are always 16 bits which should
 *   be enough, if not, there is no 32 bits jump, this case is NOT handled for now. Switch cases are 32 bits).</li>
 * <li> So such instruction must know what is its offset inside the code. They derive from OffsetInstruction
 *   which declares the offsetInstruction field.</li>
 * <li> We scan all the OffsetInstructions inside each Label, and checks if the range to their target Label
 *   is valid. If not, we transform the Instruction to a one with a bigger range (if possible), shift all
 *   the Labels and OffsetInstructions that are beyond this Instruction accordingly, and make the scan
 *   once again.</li>
 * </ul>
 * 
 * @author Julien Névo
 */
public class MethodWriter extends MethodVisitor {
	
	static final int MAXIMUM_SIGNED_VALUE_8_BITS = 0x7f;
	static final int MAXIMUM_SIGNED_VALUE_16_BITS = 0x7fff;
	static final int MINIMUM_SIGNED_VALUE_16_BITS = -0x8000;
	static final int MAXIMUM_SIGNED_VALUE_32_BITS = 0x7fffffff;
	
	/**
	 * The Class Writer of this Method Writer.
	 */
	private ClassWriter classWriter;
	
	/**
	 * The Constant Pool of the Application.
	 */
	private ConstantPool constantPool;
	
	/**
	 * Contains all the information about the Method.
	 */
	private Method method;
	
	/**
	 * List of the Pseudo Instruction encountered, linked with a Label whose offset must be set.
	 * Elements are added as we encounter switches and arrays to fill, and will be parsed at
	 * the end of the Method, in order to actually write the structures and resolve the Labels.
	 */
	private ArrayList<Instruction> pseudoInstructionList = new ArrayList<Instruction>();
	
	/**
	 * Indicates the next Line Number. It is assigned to the next Instruction found, then set to 0 at
	 * this moment, as a Line Number must be superior to 0. This Line Number is set when visiting a
	 * Line Number.
	 */
	private int nextLineNumber = 0;

	/**
	 * Constructor of the Method Writer. Requires the data of the Method invocation.
	 * @param classWriter the classWriter of the method.
	 * @param access the access flags of the method.
	 * @param name the name of the method.
	 * @param desc the descriptor of the method.
	 * @param signature the signature of the method. May be Null.
	 * @param exceptions the exceptions of the method. May be Null.
	 */
	public MethodWriter(ClassWriter classWriter, int access, String name,
			String desc, String[] signature, String[] exceptions) {
		super(Opcodes.ASM4);
		this.classWriter = classWriter;
		
		constantPool = classWriter.getConstantPool();
		
		// Adds the method to the constant pool.
		method = constantPool.addMethodToConstantPool(name, classWriter.getName(), desc,
				access, signature, exceptions);
		CodeItem codeItem = getCodeItem();
		
		if (codeItem != null) {
			// Calculate the Incoming Argument Size. This consists in parsing the descriptor, minus the
			// return type, and counting how many bytes are allocated to each type (I = 2, D = 4...).
			// Static methods don't have a "this" argument (in bytes).
			int ias = (access & Opcodes.ACC_STATIC) > 0 ? 0 : 2;
			// We now parse the descriptor, minus the return type.
			ias += Prototype.getSizeOfDescriptor(desc, true);
			codeItem.setIncomingArgumentsSizeInWord(ias / 2);
		}
	}

	
	// -----------------------------------------------------
	// Getters and Setters.
	// -----------------------------------------------------

	/**
	 * Returns the ClassWriter of this MethodWriter.
	 */
	public ClassWriter getClassWriter() {
		return classWriter;
	}
	
	/**
	 * Sets the start of the bytecode to copy from the input Dex file to the output.
	 * This is only useful when using the optimization that consists in copying part of
	 * the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked
	 * to the Writer with no Adapter to modify the methods in between.
	 * @param start start in bytes from the beginning of the Dex file where the bytecode is. This
	 *        includes the code_item header.
	 */
	public void setStartBytecodeToCopy(int start) {
		method.setStartBytecodeToCopy(start);
	}
	
	/**
	 * Returns the Method linked to this Writer.
	 * @return the Method linked to this Writer.
	 */
	public Method getMethod() {
		return method;
	}
	
	/**
	 * Returns the Code Item linked to this Writer. May be Null if it hasn't any (if abstract or interface).
	 * @return the Code Item linked to this Writer, or Null.
	 */
	public CodeItem getCodeItem() {
		return method.getCodeItem();
	}
	

	// -----------------------------------------------------
	// Public Methods.
	// -----------------------------------------------------
	
	/**
	 * Adds a label to the set of used labels. If Null, nothing is done.
	 * @param label the label to add.
	 */
	public void addLabel(Label label) {
		getCodeItem().addLabel(label);
	}
	
	/**
	 * Adds an instruction to the current Code Item. Also adds the Line Number to the
	 * Instruction, and resets it.
	 * @param insn instruction to add to the Code Item.
	 */
	public void addInstruction(Instruction insn) {
		getCodeItem().addInstruction(insn);
		
		if (nextLineNumber > 0) {
			insn.setLineNumber(nextLineNumber);
			nextLineNumber = 0;
		}
	}
	
	/**
	 * Adds padding at the end of the file, using an alignment of 4, if necessary.
	 * The padding is done using a NOP Instruction.
	 * @return the padding added, in bytes.
	 */
	public int addPadding() {
		CodeItem codeItem = getCodeItem();
		int padding = codeItem.getSize() % 4;
		if (padding != 0) {
			if (padding == 2) {
				codeItem.addInstruction(new InstructionFormat10X(0));
			} else {
				throw new RuntimeException("Padding can only be 0 or 2 ! (" + method.getMethodName() + " " + method.getClassName() + " " + codeItem.getSize() + " " + padding + ")");
			}
		}
		return padding;
	}
	
	
	// -----------------------------------------------------
	// Visitor Methods.
	// -----------------------------------------------------
	
	@Override
	public void visitCode() {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitCode.");
		}
	}
	
	@Override
	public void visitEnd() {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitEnd.");
		}

		// Now that the annotations are fully known, we can register them to the Constant Pool.
		closeAnnotations();
		
		// There's no need to check the references, parse the pseudo-instructions and generate bytecode
		// if there aren't any code.
		// This can also be the case when the ConstantPool optimization is on, as no bytecode is parsed.
		// We can free the code_item and debug_item from their stored instructions, labels and so on, in
		// order to save memory.
		CodeItem codeItem = getCodeItem();
		if (codeItem == null) {
			return;
		}
		if (codeItem.getSize() == 0) {
			method.free();
			return;
		}
		
		// We check if the Instructions referring the Labels all have a valid range. 
		// If not, the Instruction are modified.
		checkAndCorrectLabelReferences();
		
		// Then, we may have Pseudo Instructions to add at the end.
		// We do that after the Labels correction on purpose : the Pseudo Instructions may need
		// padding. It is simpler to have the final code structure before adding these Instructions,
		// so that we don't have to add/remove padding each time an Instruction is modified.
		//CodeItem codeItem = getCodeItem();
		for (Instruction insn : pseudoInstructionList) {
			addPadding();
			IPseudoInstruction ps = (IPseudoInstruction)insn;
			// Resolves the Label. Gets it from the Instruction that is linked to the Pseudo
			// Instruction and sets its offset now that we know it.
			Label label = ps.getSourceInstruction().getLabel();
			label.setOffset(codeItem.getSize());
			addInstruction(insn);
		}
		
		// We generate the bytecode now, so that we can free the code_item and debug_item from their
		// stored instructions, labels and so on, in order to save memory.
		method.generateCodeItemCode();
		method.free();
	}

	@Override
	public void visitMethodInsn(int opcode, String owner, String name,
			String desc, int[] arguments) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.print("          MethodWriter : visitMethodInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode) + ", owner = " + owner +
				", name = " + name + ", desc = " + desc + ", parameters = ");
			for (int parameter : arguments) {
				System.out.print(parameter + ", ");
			}
			System.out.println();
		}
		
		// We found an instruction related to a Method (call...). This Method could have been found before, or
		// not, in which case Constant Pool adds it. In both cases, we make the newly found Instruction
		// refer to this Method.
		Method newMethod = constantPool.addMethodToConstantPool(name, owner, desc, Opcodes.ACC_UNKNOWN, null, null);
		Instruction insn = InstructionEncoder.encodeMethodInsn(opcode, newMethod, arguments);
		addInstruction(insn);
		
		CodeItem codeItem = getCodeItem();
		
		// Calculates the Outgoing Arguments Size.
		int storedOutgoingSize = codeItem.getOutgoingArgumentsSizeInWord();
		// We now parse the descriptor, minus the return type.
		int outgoingSize = (Prototype.getSizeOfDescriptor(desc, true) / 2);
		// If the opcode of the call shows a non-static call, we have to count "this" in
		// the current Outgoing Arguments size.
		if (opcode != Opcodes.INSN_INVOKE_STATIC && opcode != Opcodes.INSN_INVOKE_STATIC_RANGE) {
			outgoingSize++;
		}
		
		if (outgoingSize > storedOutgoingSize) {
			codeItem.setOutgoingArgumentsSizeInWord(outgoingSize);
		}
		
		// We add the Prototype to the Prototype pool.
		Prototype prototype = constantPool.addPrototypeToConstantPool(desc);
		
		// Adds the Types to the constant pool. Then can be found in the newly created Prototype.
		constantPool.addTypeListToConstantPool(prototype.getParameterTypes());
		
	}

	@Override
	public void visitParameters(String[] parameters) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.print("          MethodWriter : visitParameters(). Parameters = ");
			if (parameters != null) {
				for (String parameter : parameters) {
					System.out.print(parameter + ", ");
				}
			}
			System.out.println();
		}
		
		// Adds the Parameters as Strings in the constant pool, but ONLY if there are not empty.
		// As we don't refer to them in the Debug Item, it is not useful to encode an empty String.
		if (parameters != null) {
			for (String parameter : parameters) {
				if (!parameter.equals("")) {
					constantPool.addStringToConstantPool(parameter);
				}
			}
		}
		
		method.setParameters(parameters);
	}

	@Override
	public void visitVarInsn(int opcode, int destinationRegister, int var) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitVarInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister +
				", var = 0x" + Integer.toHexString(var)
				);
		}
		
		Instruction insn = InstructionEncoder.encodeVarInsn(opcode, destinationRegister, var);
		addInstruction(insn);
	}

	@Override
	public void visitVarInsn(int opcode, int destinationRegister, long var) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitVarInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister +
				", var (long) = 0x" + Long.toHexString(var)
				);
		}
		
		Instruction insn = InstructionEncoder.encodeVarInsn(opcode, destinationRegister, var);
		addInstruction(insn);
	}
	

	@Override
	public void visitInsn(int opcode) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode));
		}

		addInstruction(InstructionEncoder.encodeInsn(opcode));
	}
	
	@Override
	public void visitLabel(Label label) {
		CodeItem codeItem = getCodeItem();
		label.setOffset(codeItem.getSize());
		addLabel(label);
		
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitLabel(). Label = " + label);
		}
	}


	
	
	@Override
	public void visitOperationInsn(int opcode, int destinationRegister,
			int firstSourceRegister, int secondSourceRegister, int value) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitOperationInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode)
				+ ", destinationRegister = " + destinationRegister
				+ ", firstSourceRegister = " + firstSourceRegister
				+ ", secondSourceRegister = " + secondSourceRegister
				+ ", value = 0x" + Integer.toHexString(value)
				);
		}
		
		Instruction insn = InstructionEncoder.encodeOperationInsn(opcode, destinationRegister,
				firstSourceRegister, secondSourceRegister, value);
		addInstruction(insn);
	}
	
	@Override
	public void visitIntInsn(int opcode, int register) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitIntInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode)
				+ ", operand = " + register);
		}
		Instruction insn = InstructionEncoder.encodeIntInsn(opcode, register);
		addInstruction(insn);
	}

	@Override
	public void visitJumpInsn(int opcode, Label label, int registerA,
			int registerB) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitJumpInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode)
				+ ", label = " + label
				+ ", destinationRegister = " + registerA
				+ ", firstSourceRegister = " + registerB);
		}
		
		// Note that we also provide the instruction offset so that the instruction knows its offset,
		// to help calculate later if the Label it refers is within range or not. 
		CodeItem codeItem = getCodeItem();
		Instruction insn = InstructionEncoder.encodeJumpInsn(opcode, label, registerA, registerB, codeItem.getSize());
		
		// Make the label refer to the instruction, at this specific offset.
		label.addReferringInstruction(insn);

		addInstruction(insn);
		addLabel(label);
	}
	
	@Override
	public void visitFillArrayDataInsn(int arrayReference, Object[] arrayData) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.print("          MethodWriter : visitFillArrayDataInsn(). "
					+ "ArrayReference = " + Integer.toHexString(arrayReference)
					+ ", arrayData = ");
			for (Object data : arrayData) {
				System.out.print(data + ", ");
			}
			System.out.println();
		}
		
		// The array data are given by the parameters. However, they are not encoded directly in the
		// Dalvik instruction now, but as a reference. First we encode the fill-array-data instruction,
		// with a link to a Label where we will encode the array.
		CodeItem codeItem = getCodeItem();
		Label arrayLabel = new Label();
		Instruction insn = InstructionEncoder.encodeFillArrayDataInsn(0x26, arrayReference, arrayLabel, codeItem.getSize());
		addInstruction(insn);
		// Adds the fill-array-data pseudo instruction in a List to be parsed at the end of the
		// method. We'll encode the array here.
		Instruction ps = new PseudoInstructionFillArrayData(arrayData, (IOffsetInstruction)insn);
		pseudoInstructionList.add(ps);
	}

	@Override
	public void visitTypeInsn(int opcode, int destinationRegister,
			int referenceBearingRegister, int sizeRegister, String type) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitTypeInsn(). Opcode = 0x" + 
				Integer.toHexString(opcode)
				+ ", destinationRegister = " + destinationRegister
				+ ", refBearingRegister = " + referenceBearingRegister
				+ ", sizeRegister = " + sizeRegister
				+ ", type = " + type);
		}

		// Registers the given Type.
		constantPool.addTypeToConstantPool(type);
		
		Instruction insn = InstructionEncoder.encodeTypeInsn(opcode, destinationRegister,
				referenceBearingRegister, sizeRegister, type);
		addInstruction(insn);
	}
	
	@Override
	public void visitMultiANewArrayInsn(String desc, int[] registers) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.print("          MethodWriter : visitMultiANewArrayInsn(). "
				+ "Desc = " + desc + ", registers = ");
			for (int register : registers) {
				System.out.print("0x" + Integer.toHexString(register) + ", ");
			}
			System.out.println();
		}
		
		// Registers the given Type.
		constantPool.addTypeToConstantPool(desc);
		
		Instruction insn = InstructionEncoder.encodeMultiANewArrayInsn(desc, registers);
		addInstruction(insn);
	}
	
	@Override
	public void visitTableSwitchInsn(int register, int min, int max,
			Label dflt, Label[] labels) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.print("          MethodWriter : visitTableSwitchInsn(). "
					+ "Register = 0x" + Integer.toHexString(register)
					+ ", min = " + min
					+ ", max = " + max
					+ ", default = " + dflt + ". Labels = "
			);
			for (Label label : labels) {
				System.out.print(label + ", ");
			}
			System.out.println();
		}
		
		// The max parameter is not used (can be calculated if needed).
		// The Default Label is also not used. It is not encoded in the Table (unless to fill the "holes"
		// in a Switch/Case, but it then appears as normal entries in the Table). The Default code is
		// encoded just after the Switch/Case.
		
		// The Table Switch is composed of the Instruction, plus the Table (the Pseudo Instruction).
		// However, this one is not encoded directly in the Instruction, but as a reference.
		// First we encode the Instruction, with a Label that has no offset for now.
		Label switchLabel = new Label();
		CodeItem codeItem = getCodeItem();
		Instruction insn = InstructionEncoder.encodeTableSwitchInsn(register, switchLabel, codeItem.getSize());
		addInstruction(insn);
		
		// Adds the packed-switch pseudo instruction in a List to be parsed at the end of the
		// method. We'll encode the table here.
		Instruction ps = new PseudoInstructionPackedSwitch(min, labels, (IOffsetInstruction)insn);
		pseudoInstructionList.add(ps);
	}
	
	@Override
	public void visitLookupSwitchInsn(int register, Label dflt, int[] keys, Label[] labels) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
				System.out.print("          MethodWriter : visitLookupSwitchInsn(). "
					+ "Register = 0x" + Integer.toHexString(register)
					+ ", default = " + dflt + ". Keys+Labels = "
			);
			for (int i = 0; i < keys.length; i++) {
				System.out.print(keys[i] +"-->" + labels[i] + ", ");
			}
			System.out.println();
		}
		
		// The Default Label is not used, as it is not encoded in the Table.
		
		// The Sparse Switch is composed of the Instruction, plus the Table. However, this one is not encoded
		// directly in the Instruction, but as a reference. First we encode the Instruction,
		// with a link to a Label where we will encode the Table.
		CodeItem codeItem = getCodeItem();
		Label switchLabel = new Label();
		Instruction insn = InstructionEncoder.encodeSparseSwitchInsn(register, switchLabel, codeItem.getSize());
		addInstruction(insn);
		
		// Adds the sparse-switch pseudo instruction in a List to be parsed at the end of the
		// method. We'll encode the table here.
		Instruction ps = new PseudoInstructionSparseSwitch(keys, labels, (IOffsetInstruction)insn);
		pseudoInstructionList.add(ps);
	}
	
	@Override
	public void visitTryCatchBlock(Label start, Label end, Label handler,
			String type) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitTryCatchBlock(). "
				+ "start = " + start
				+ ", end = " + end
				+ ", handler = " + handler);
		}
		
		// Registers the given Type.
		constantPool.addTypeToConstantPool(type);
		
		addLabel(end);
		addLabel(handler);
		
		// A Try/Catch block isn't encoded as an Instruction. We store it as is for it to be encoded
		// when the code_item has to be. The Try/Catch are encoded after the Instructions.
		CodeItem codeItem = getCodeItem();
		codeItem.addTryCatch(new TryCatch(start, end, new ExceptionHandler(handler, type)));
	}

	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitAnnotation(). "
				+ "Desc = " + desc
				+ ", visible = " + visible
			);
		}
		
		// Creates an AnnotationItem, incomplete for now (no Elements), and registers it to the Method.
		// We do not add it to the Constant Pool now because of this incompleteness.
		AnnotationWriter annotationWriter = AnnotationWriter.createAnnotationWriter(desc, visible, constantPool, null);
		method.addAnnotationItem(annotationWriter.getAnnotationItem());
		
		return annotationWriter;
	}
	
	@Override
	public AnnotationVisitor visitParameterAnnotation(int parameter,
			String desc, boolean visible) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("    MethodWriter : visitParameterAnnotation. Parameter = " + parameter
				+ ", desc = " + desc
				+ ", visible = " + visible
				);
		}
		
		// Creates an AnnotationItem, incomplete for now (no Elements), and registers it to the Method.
		// We do not add it to the Constant Pool now because of this incompleteness.
		AnnotationItem annotationItem = new AnnotationItem(visible, desc);
		method.addParameterAnnotationItem(parameter, annotationItem);
		
		// Adds the desc to the Constant Pool.
		constantPool.addTypeToConstantPool(desc);
		
		return new AnnotationWriter(constantPool, annotationItem);
	}

	@Override
	public void visitArrayLengthInsn(int destinationRegister,
			int arrayReferenceBearing) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitArrayLength(). "
				+ "destinationRegister = " + destinationRegister
				+ ", arrayReferenceBearing = " + arrayReferenceBearing);
		}

		Instruction insn = InstructionEncoder.encodeArrayLength(destinationRegister, arrayReferenceBearing);
		addInstruction(insn);
	}

	
	@Override
	public void visitArrayOperationInsn(int opcode, int valueRegister,
			int arrayRegister, int indexRegister) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitArrayOperationInsn(). "
					+ "opcode = " + Integer.toHexString(opcode)
					+ ", valueRegister = " + valueRegister
					+ ", arrayRegister = " + arrayRegister
					+ ", indexRegister = " + indexRegister
			);
		}
		
		Instruction insn = InstructionEncoder.encodeArrayOperation(opcode, valueRegister, arrayRegister, indexRegister);
		addInstruction(insn);
	}
	
	@Override
	public void visitStringInsn(int opcode, int destinationRegister, String string) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitStringInsn(). "
				+ "opcode = " + Integer.toHexString(opcode)
				+ ", destinationRegister = " + destinationRegister
				+ ", string = " + string
			);
		}
		
		// Adds the string to the Constant Pool.
		constantPool.addStringToConstantPool(string);
		
		Instruction insn = InstructionEncoder.encodeStringOperation(opcode, destinationRegister, string);
		addInstruction(insn);
	}

	@Override
	public void visitFieldInsn(int opcode, String owner, String name,
			String desc, int valueRegister, int objectRegister) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitFieldInsn(). "
					+ "opcode = " + Integer.toHexString(opcode)
					+ ", owner = " + owner
					+ ", name = " + name
					+ ", desc = " + desc
					+ ", valueRegister = " + valueRegister
					+ ", objectRegister = " + objectRegister
			);
		}
		
		// We found an instruction related to a Field. This Field could have been found before, or not, in
		// which case the Constant Pool will add it. In both cases, we make the newly found instruction
		// refer to this Field.
		Field field = constantPool.addFieldToConstantPool(name, desc, owner, Opcodes.ACC_UNKNOWN, null, null);

		Instruction insn = InstructionEncoder.encodeFieldInsn(opcode, valueRegister, objectRegister, field);
		if (insn != null) addInstruction(insn);
		else throw new RuntimeException("MethodWriter.visitFieldInsn");
	}

	@Override
	public void visitLocalVariable(String name, String desc, String signature,
			Label start, Label end, int index) {
		List<Label> ends = null;
		if (end != null) {
			ends = new ArrayList<Label>(1);
			ends.add(end);
		}
		visitLocalVariable(name, desc, signature, start, ends, null, index);
	}
	
	@Override
	public void visitLocalVariable(String name, String desc, String signature,
			Label start, List<Label> ends, List<Label> restarts, int index) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitLocalVariable(). "
				+ "Name = " + name
				+ ", desc = " + desc
				+ ", signature = " + signature
				+ ", start = " + start
				+ ", index = " + index
			);
			System.out.print("             LabelEnds = ");
			if (ends != null) {
				for (Label l: ends) {
					System.out.print(l +" --- ");
				}
			}
			System.out.println();
			System.out.print("             LabelRestarts = ");
			if (restarts != null) {
				for (Label l: restarts) {
					System.out.print(l +" --- ");
				}
			}	
			System.out.println();
		}
		
		constantPool.addStringToConstantPool(name);
		constantPool.addTypeToConstantPool(desc);
		constantPool.addStringToConstantPool(signature);
		
		LocalVariable localVariable = new LocalVariable(index, name, desc, signature, start, ends, restarts);
		method.addLocalVariable(localVariable);
	}

	@Override
	public void visitLineNumber(int line, Label start) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitLineNumber(). "
				+ "Line = " + line
				+ ", start = " + start
			);
		}
		
		// We don't use the Start Label. There's always a visitLabel before...
		nextLineNumber = line;
		method.setFirstLineNumberIfNeeded(nextLineNumber);
	}	

	@Override
	public void visitMaxs(int maxStack, int maxLocals) {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitMaxs(). MaxStack = " + maxStack);
		}
		
		// maxLocals is ignored by AsmDex.
		method.getCodeItem().setRegisterSize(maxStack);
	}
	
	@Override
	public AnnotationVisitor visitAnnotationDefault() {
		if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) {
			System.out.println("          MethodWriter : visitAnnotationDefault.");
		}
		
		return AnnotationWriter.createAnnotationWriter(Constants.ANNOTATION_DEFAULT_INTERNAL_NAME, false, constantPool,
				classWriter.getClassDefinitionItem());
	}

	@Override
	public void visitAttribute(Object attr) {
		// Method ignored by AsmDex. Attributes are not supported.
	}

	@Override
	public void visitFrame(int type, int nLocal, Object[] local, int nStack,
			Object[] stack) {
		// Method ignored by AsmDex. Attributes are not supported.
	}
	
	
	// ---------------------------------------------------
	// Private methods.
	// ---------------------------------------------------
	
	/**
	 * Checks if the Instructions making references to each Label is valid. That is, that the range of
	 * its instruction is enough. If not, corrects it by modifying the Instruction.
	 */
	private void checkAndCorrectLabelReferences() {
		
		boolean madeChange;
		CodeItem codeItem = getCodeItem();
		List<Label> labels = codeItem.getLabels();

		// Parses all the Labels, and all the OffsetInstructions inside the Labels.
		// We stop the parsing whenever a change of instruction has been made, to restart the parsing
		// once again till everything is valid.
		do {
			madeChange = false;
			Iterator<Label> labelIterator = labels.iterator();
			while (!madeChange && labelIterator.hasNext()) {
				Label label = labelIterator.next();
				ArrayList<Instruction> instructions = label.getReferringInstructions();
	
				int insnIndex = 0;
				int nbInsn = instructions.size();
				while (!madeChange && (insnIndex < nbInsn)) {
					Instruction insn = instructions.get(insnIndex);
					
					IOffsetInstruction offsetInsn = (IOffsetInstruction)insn;
					int instructionOffset = offsetInsn.getInstructionOffset();
					// The offset have to be divided by two, because the encoded offset is word based.
					// The relativeOffset is the offset for the Instruction to reach the Label.
					// It is negative if the Label is before the Instruction.
					int relativeOffset = (label.getOffset() - instructionOffset) / 2;
					
					int opcode = insn.getOpcodeByte();
					int maximumOffset = 0;
					// Check if the relative offset is valid for the instruction range. 
					switch (opcode) {
					case 0x28: // Goto 8 bits.
						maximumOffset = MAXIMUM_SIGNED_VALUE_8_BITS;
						break;
					case 0x29: // Goto 16 bits.
					case 0x32: // If-test.
					case 0x33:
					case 0x34:
					case 0x35:
					case 0x36:
					case 0x37:
					case 0x38: // If-testz.
					case 0x39:
					case 0x3a:
					case 0x3b:
					case 0x3c:
					case 0x3d:
						maximumOffset = MAXIMUM_SIGNED_VALUE_16_BITS;
						break;
					case 0x2a: // Goto 32 bits.
					case 0x2b: // Packed Switch.
					case 0x2c: // Sparse Switch.
						maximumOffset = MAXIMUM_SIGNED_VALUE_32_BITS;
						break;
					default:
						try { throw new Exception("Opcode error : 0x" + Integer.toHexString(opcode)); } catch (Exception e) { e.printStackTrace(); }
					}
					
					int minimumOffset = -maximumOffset - 1;
					
					if ((relativeOffset < minimumOffset) || (relativeOffset > maximumOffset)) {
						// Must change to an Instruction with a bigger range. This is only possible for
						// the GOTO 8/16 bits.
						if ((opcode == Opcodes.INSN_GOTO) || (opcode == Opcodes.INSN_GOTO_16)) {
							Instruction newInsn;
							// Change to 16 or 32 bits ?
							if ((relativeOffset > MAXIMUM_SIGNED_VALUE_16_BITS) || (relativeOffset < MINIMUM_SIGNED_VALUE_16_BITS)) {
								// 32 bits.
								newInsn = new InstructionFormat30T(Opcodes.INSN_GOTO_32, label, instructionOffset);
							} else {
								// 16 bits.
								newInsn = new InstructionFormat20T(Opcodes.INSN_GOTO_16, label, instructionOffset);
							}
	
							// Removes the instruction from the codeItem and replaces it with the new one.
							codeItem.replaceInstructions(insn, newInsn);
							
							// Replaces the old instruction with the new one in the Label Instructions list.
							instructions.remove(insnIndex);
							instructions.add(insnIndex, newInsn);
							
							// Shifts all the Jump related instructions and the labels AFTER this instruction.
							shiftOffsetInstructionsAndLabels(instructionOffset, newInsn.getSize() - insn.getSize());
							madeChange = true;
							
						} else {
							try { throw new IllegalArgumentException("Instruction Range extension unhandled. Opcode : 0x" + Integer.toHexString(opcode)); } catch (Exception e) { e.printStackTrace(); }
						}
					}
					
					insnIndex++;
				}
				
			}
		} while (madeChange);
	}
	
	/**
	 * Shifts all the Offset Instructions and Labels of a given number of bytes, from the <i>exclusive</i>
	 * offset given. All labels and instructions below or equal are ignored.
	 * @param shiftOffset <i>exclusive</i> offset from which the shift begins.
	 * @param shiftSize shift in bytes to add.
	 */
	private void shiftOffsetInstructionsAndLabels(int shiftOffset, int shiftSize) {
		if (shiftSize != 0) {
			CodeItem codeItem = getCodeItem();
			List<Label> labels = codeItem.getLabels();
			for (Label label : labels) {
				// Shifts the Label offset if needed.
				int labelOffset = label.getOffset();
				if (labelOffset > shiftOffset) {
					label.setOffset(labelOffset + shiftSize);
				}
				// Scans all its instructions and shifts them if needed.
				for (Instruction instruction : label.getReferringInstructions()) {
					IOffsetInstruction offsetInstruction = (IOffsetInstruction)instruction;
					int instructionOffset = offsetInstruction.getInstructionOffset();
					if (instructionOffset > shiftOffset) {
						offsetInstruction.setInstructionOffset(instructionOffset + shiftSize);
					}
				}
			}
		}
	}

	/**
	 * Closes and registers the annotation_set_items and annotation_set_ref_items of this Method.
	 * This must only be done once when the Method has been fully visited. 
	 */
	public void closeAnnotations() {
		method.closeAnnotations(constantPool);	
	}
}
